using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Timers;
using PSE.Hosting.ServiceHoster.Business;
using PSE.Hosting.ServiceHoster.Configuration;
using PSE.Hosting.ServiceHoster.Logging;

namespace PSE.Hosting.ServiceHoster.Process
{
    public delegate void AssemblyEventHandler(object source, AssemblyEventArgs eventArgs);

    internal class AssemblyState : IDisposable
    {
        #region Private Fields

        private string _packageName;
        private string _codeBase;
        private string _shadowCodeBase;
        private bool _isValid;
        private bool _raiseEvents;
        private Timer _timer;
        private ServicesLoader _proxy;
        private FileSystemWatcher _fileSystemWatcher;
        public event AssemblyEventHandler AssemblyDeleted;
        public event AssemblyEventHandler AssemblyChanged;
        protected bool _disposed = false;

        #endregion

        #region Constructors

        public AssemblyState(string packageName, string codeBase)
        {
            if (!File.Exists(codeBase))
                throw new FileNotFoundException(codeBase);

            _packageName = packageName;
            _codeBase = codeBase;
            _isValid = false;
            _raiseEvents = false;

            _timer = new Timer();
            _timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
            _timer.Interval = 1000;
            _timer.Enabled = true;

            _fileSystemWatcher = new FileSystemWatcher(Path.GetDirectoryName(_codeBase), Path.GetFileNameWithoutExtension(_codeBase) + "*");
            _fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName;
            _fileSystemWatcher.IncludeSubdirectories = false;
            _fileSystemWatcher.Changed += new FileSystemEventHandler(_fileSystemWatcher_Changed);
            _fileSystemWatcher.Deleted += new FileSystemEventHandler(_fileSystemWatcher_Deleted);
            _fileSystemWatcher.Error += new ErrorEventHandler(_fileSystemWatcher_Error);
            _fileSystemWatcher.InternalBufferSize = 64 * 4096;

            Program.OpenServiceDomain(Path.GetDirectoryName(_codeBase));

            Program.MergeConfigurationFile(_codeBase + ".config");
        }

        ~AssemblyState()
        {
            Dispose(false);
        }

        #endregion

        #region Properties

        public string FileName
        {
            get { return Path.GetFileName(_codeBase); }
        }

        public string AssemblyFullName
        {
            get
            {
                if (_proxy != null)
                {
                    return _proxy.GetAssemblyName();
                }
                else
                {
                    return string.Empty;
                }
            }
        }

        public string CodeBase
        {
            get { return _codeBase; }
        }

        public bool IsValid
        {
            get { return _isValid; }
            set { _isValid = value; }
        }

        #endregion

        #region Public Methods

        public Dictionary<string, ServiceData> GetServices()
        {
            return GetProxyInstance().GetServices();
        }

        public Dictionary<string, Exception> StartAll()
        {
            Dictionary<string, Exception> exceptions = null;

            RefreshSections();

            exceptions = GetProxyInstance().StartAll();

            _fileSystemWatcher.EnableRaisingEvents = true;

            return exceptions;
        }

        public void Start(string serviceName)
        {
            RefreshSections();

            GetProxyInstance().Start(serviceName);

            _fileSystemWatcher.EnableRaisingEvents = true;
        }

        public void StopAll()
        {
            _fileSystemWatcher.EnableRaisingEvents = false;

            GetProxyInstance().StopAll();
        }

        public void Stop(string serviceName)
        {
            _fileSystemWatcher.EnableRaisingEvents = false;

            GetProxyInstance().Stop(serviceName, true);
        }

        public void RefreshSections()
        {
            GetProxyInstance().RefreshSections();
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {

                }

                if (_proxy != null)
                {
                    _proxy.StopAll();
                    _proxy = null;
                }

                if (_fileSystemWatcher != null)
                {
                    _fileSystemWatcher.Dispose();
                    _fileSystemWatcher = null;
                }

                if (_timer != null)
                {
                    _timer.Dispose();
                    _timer = null;
                }

                _disposed = true;
            }
        }

        #endregion

        #region Private Methods

        private void _fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
        {
            if (Path.GetExtension(e.FullPath) == ".dll")
            {
                AssemblyEventArgs eventArgs = new AssemblyEventArgs();
                eventArgs.CodeBase = e.FullPath;
                eventArgs.PackageName = _packageName;

                if (AssemblyDeleted != null)
                    AssemblyDeleted(this, eventArgs);
            }
        }

        private void _fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            if (_raiseEvents || Path.GetExtension(e.FullPath) == ".dll")
            {
                _timer.Enabled = false;
                _raiseEvents = false;

                AssemblyEventArgs eventArgs = new AssemblyEventArgs();
                eventArgs.CodeBase = e.FullPath;
                eventArgs.PackageName = _packageName;

                if (AssemblyChanged != null)
                    AssemblyChanged(this, eventArgs);

                _timer.Enabled = true;
            }
        }

        private void _fileSystemWatcher_Error(object sender, ErrorEventArgs e)
        {
            Console.WriteLine("ERROR: " + e.GetException().Message);
            Logger.Log(e.GetException(), Logger.EntryType.Error, Logger.Source.ServiceHoster);
        }

        private void _timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            _raiseEvents = true;
        }

        private ServicesLoader GetProxyInstance()
        {
            if (_proxy == null)
            {
                InitializeServiceInstance();
            }

            return _proxy;
        }

        internal void InitializeServiceInstance()
        {
            GenerateShadownFile();

            SetFolderListCache();

            _proxy = (ServicesLoader)Program.ServiceDomain.CreateInstanceFromAndUnwrap(
                Assembly.GetExecutingAssembly().CodeBase, typeof(ServicesLoader).FullName);

            _proxy.LoadServicesAssembly(_shadowCodeBase);
        }

        private void GenerateShadownFile()
        {
            Program.ServiceDomain.SetData("ShadowCodeBaseDirectory", Program.ShadowBaseDirectory);

            int count = Directory.GetFiles(Program.ShadowBaseDirectory, Path.GetFileNameWithoutExtension(_codeBase) + "*.dll").Length + 1;

            _shadowCodeBase = string.Format(@"{0}\{1}{2}{3}", Program.ShadowBaseDirectory, Path.GetFileNameWithoutExtension(_codeBase), count, ".dll");

            File.Copy(_codeBase, _shadowCodeBase);

            if (File.Exists(_codeBase + ".config"))
            {
                File.Copy(_codeBase + ".config", _shadowCodeBase + ".config");
            }
        }

        private void SetFolderListCache()
        {
            List<string> folderList = (List<string>)Program.ServiceDomain.GetData("FolderList");

            if (folderList == null)
            {
                folderList = new List<string>();
            }

            if (!folderList.Contains(Path.GetDirectoryName(_codeBase)))
                folderList.Add(Path.GetDirectoryName(_codeBase));

            Program.ServiceDomain.SetData("FolderList", folderList);
        }

        #endregion
    }
}